// SlowMethodCaching.aj
//
// Informatics 102 Spring 2012
// Code Example
//
// This aspect will weave caching into the slowMethod() in our
// ClassWithSlowMethod.  The strategy is to do this:
//
// * Create a HashMap, whose job is to store the result associated
//   with each possible input to the method.  We'll call that map
//   our "cache."
// * Each time the method is called, check the cache to see if we've
//   stored a result for the given input.  If we have, skip the slow
//   method and return the result from before.  If we haven't, execute
//   the slow method, then save the result into the cache before
//   returning it.
//
// So our first call to slowMethod() with a particular argument will
// always be slow, but subsequent accesses will be fast.

import java.util.HashMap;


public aspect SlowMethodCaching
{
	// Since we only need one cache, we'll store it in our aspect.
	// We could also have injected it into the ClassWithSlowMethod
	// as a static field using an inter-type declaration.
	private HashMap<String, String> cache = new HashMap<String, String>();


	// This pointcut matches calls to our slow method, also hoisting
	// the value of the method's argument into the pointcut parameter,
	// so that it will be available in advice.
	pointcut slowMethodCalls(String arg):
		call(public static String ClassWithSlowMethod.slowMethod(String))
		&& args(arg);
	

	// Around advice that replaces code that returns a value -- such as
	// this advice, which we're using to replace a call to a method that
	// returns a String -- also needs to return a value of the same
	// type.  That means we need to be sure to use "return" statements,
	// just as we would in a method that returns String.
	String around(String arg):
		slowMethodCalls(arg)
	{
		if (cache.containsKey(arg))
		{
			return cache.get(arg);
		}
		else
		{
			String value = proceed(arg);
			cache.put(arg, value);
			return value;
		}
	}
}

// There are other things that need to be considered when caching,
// which we didn't talk about, but that are necessary in a realistic
// implementation.  For example, it may be necessary to "flush" the
// cache once in a while by removing values that haven't been used
// for a while.  This keeps the cache from growing out of control.
// It may also be necessary for values in a cache to have a lifespan,
// if we know that the value isn't necessarily going to correct forever,
// but we have a good reason to believe it will be correct for a
// certain length of time.
